package eu.catengine.renderable.sprite;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import javax.microedition.khronos.opengles.GL10;

import eu.catengine.common.iface.ISprite;
import eu.catengine.common.listener.AnimationListener;
import eu.catengine.graphic.TextureAnimation;
import eu.catengine.primitive.Point;
import eu.catengine.primitive.Size;

/**
 * Klasa przechowująca dane dotyczące animacji na podstawie sprite'a
 * wyświetlanego w scenie.<br>
 * <br>
 * Punt zaczepienia obiektu (anchor point) znajduje się w lewym górnym rogu.
 * 
 * @author mbienkowski
 */
public class AnimatedSprite extends Sprite implements ISprite
{
    private TextureAnimation mTextureAnimation;
    private long mMaxLoops;
    private long mActualLoop;
    private long mActualFrame;
    private AnimationListener mAnimationListener;
    private boolean mNotCreatedInScene;

    /**
     * Tworzy animację.
     * 
     * @param point
     *            <code>Point</code> Punkt zaczepienia.
     * @param animation
     *            <code>TextureAnimation</code> Tekstura animacji z
     *            AnimationManager.
     * @param animationLoops
     *            <code>long</code> Ilość odtwarzanych animacji. <br>
     *            Dla <code>animationLoops = -1</code> ilość odtwarzanych
     *            animacji wynosi nieskończoność.
     */
    public AnimatedSprite(Point point, TextureAnimation animation, long animationLoops)
    {
	this.mActualFrame = 0;
	this.mActualLoop = 0;
	this.mMaxLoops = animationLoops;

	this.setAnimatedSpriteData("", point.x, point.y, animation.getSize().width, animation.getSize().height, animation);
    }

    /**
     * Tworzy animację.
     * 
     * @param x
     *            <code>float</code> Współrzędna X punktu zaczepienia.
     * @param y
     *            <code>float</code> Współrzędna Y punktu zaczepienia.
     * @param animation
     *            <code>TextureAnimation</code> Tekstura animacji z
     *            AnimationManager.
     * @param animationLoops
     *            <code>long</code> Ilość odtwarzanych animacji. <br>
     *            Dla <code>animationLoops = -1</code> ilość odtwarzanych
     *            animacji wynosi nieskończoność.
     */
    public AnimatedSprite(float x, float y, TextureAnimation animation, long animationLoops)
    {
	this.mActualFrame = 0;
	this.mActualLoop = 0;
	this.mMaxLoops = animationLoops;

	this.setAnimatedSpriteData("", x, y, animation.getSize().width, animation.getSize().height, animation);
    }

    /**
     * Tworzy animację.
     * 
     * @param point
     *            <code>Point</code> Punkt zaczepienia.
     * @param size
     *            <code>Size</code> Rozmiar animacji w scenie.
     * @param animation
     *            <code>TextureAnimation</code> Tekstura animacji z
     *            AnimationManager.
     * @param animationLoops
     *            <code>long</code> Ilość odtwarzanych animacji. <br>
     *            Dla <code>animationLoops = -1</code> ilość odtwarzanych
     *            animacji wynosi nieskończoność.
     */
    public AnimatedSprite(Point point, Size size, TextureAnimation animation, long animationLoops)
    {
	this.mActualFrame = 0;
	this.mActualLoop = 0;
	this.mMaxLoops = animationLoops;

	this.setAnimatedSpriteData("", point.x, point.y, size.width, size.height, animation);
    }

    /**
     * Tworzy animację.
     * 
     * @param x
     *            <code>float</code> Współrzędna X punktu zaczepienia.
     * @param y
     *            <code>float</code> Współrzędna Y punktu zaczepienia.
     * @param size
     *            <code>Size</code> Rozmiar animacji w scenie.
     * @param animation
     *            <code>TextureAnimation</code> Tekstura animacji z
     *            AnimationManager.
     * @param animationLoops
     *            <code>long</code> Ilość odtwarzanych animacji. <br>
     *            Dla <code>animationLoops = -1</code> ilość odtwarzanych
     *            animacji wynosi nieskończoność.
     */
    public AnimatedSprite(float x, float y, Size size, TextureAnimation animation, long animationLoops)
    {
	this.mActualFrame = 0;
	this.mActualLoop = 0;
	this.mMaxLoops = animationLoops;

	this.setAnimatedSpriteData("", x, y, size.width, size.height, animation);
    }

    /**
     * Tworzy animację.
     * 
     * @param x
     *            <code>float</code> Współrzędna X punktu zaczepienia.
     * @param y
     *            <code>float</code> Współrzędna Y punktu zaczepienia.
     * @param width
     *            <code>float</code> Szerokość.
     * @param height
     *            <code>float</code> Wysokość.
     * @param animation
     *            <code>TextureAnimation</code> Tekstura animacji z
     *            AnimationManager.
     * @param animationLoops
     *            <code>long</code> Ilość odtwarzanych animacji. <br>
     *            Dla <code>animationLoops = -1</code> ilość odtwarzanych
     *            animacji wynosi nieskończoność.
     */
    public AnimatedSprite(float x, float y, float width, float height, TextureAnimation animation, long animationLoops)
    {
	this.mActualFrame = 0;
	this.mActualLoop = 0;
	this.mMaxLoops = animationLoops;

	this.setAnimatedSpriteData("", x, y, width, height, animation);
    }

    /**
     * Tworzy animację.
     * 
     * @param id
     *            <code>String</code> Nazwa obiektu.
     * @param point
     *            <code>Point</code> Punkt zaczepienia.
     * @param animation
     *            <code>TextureAnimation</code> Tekstura animacji z
     *            AnimationManager.
     * @param animationLoops
     *            <code>long</code> Ilość odtwarzanych animacji. <br>
     *            Dla <code>animationLoops = -1</code> ilość odtwarzanych
     *            animacji wynosi nieskończoność.
     */
    public AnimatedSprite(String id, Point point, TextureAnimation animation, long animationLoops)
    {
	this.mActualFrame = 0;
	this.mActualLoop = 0;
	this.mMaxLoops = animationLoops;

	this.setAnimatedSpriteData(id, point.x, point.y, animation.getSize().width, animation.getSize().height, animation);
    }

    /**
     * Tworzy animację.
     * 
     * @param id
     *            <code>String</code> Nazwa obiektu.
     * @param x
     *            <code>float</code> Współrzędna X punktu zaczepienia.
     * @param y
     *            <code>float</code> Współrzędna Y punktu zaczepienia.
     * @param animation
     *            <code>TextureAnimation</code> Tekstura animacji z
     *            AnimationManager.
     * @param animationLoops
     *            <code>long</code> Ilość odtwarzanych animacji. <br>
     *            Dla <code>animationLoops = -1</code> ilość odtwarzanych
     *            animacji wynosi nieskończoność.
     */
    public AnimatedSprite(String id, float x, float y, TextureAnimation animation, long animationLoops)
    {
	this.mActualFrame = 0;
	this.mActualLoop = 0;
	this.mMaxLoops = animationLoops;

	this.setAnimatedSpriteData(id, x, y, animation.getSize().width, animation.getSize().height, animation);
    }

    /**
     * Tworzy animację.
     * 
     * @param id
     *            <code>String</code> Nazwa obiektu.
     * @param point
     *            <code>Point</code> Punkt zaczepienia.
     * @param size
     *            <code>Size</code> Rozmiar animacji w scenie.
     * @param animation
     *            <code>TextureAnimation</code> Tekstura animacji z
     *            AnimationManager.
     * @param animationLoops
     *            <code>long</code> Ilość odtwarzanych animacji. <br>
     *            Dla <code>animationLoops = -1</code> ilość odtwarzanych
     *            animacji wynosi nieskończoność.
     */
    public AnimatedSprite(String id, Point point, Size size, TextureAnimation animation, long animationLoops)
    {
	this.mActualFrame = 0;
	this.mActualLoop = 0;
	this.mMaxLoops = animationLoops;

	this.setAnimatedSpriteData(id, point.x, point.y, size.width, size.height, animation);
    }

    /**
     * Tworzy animację.
     * 
     * @param id
     *            <code>String</code> Nazwa obiektu.
     * @param x
     *            <code>float</code> Współrzędna X punktu zaczepienia.
     * @param y
     *            <code>float</code> Współrzędna Y punktu zaczepienia.
     * @param width
     *            <code>float</code> Szerokość.
     * @param height
     *            <code>float</code> Wysokość.
     * @param animation
     *            <code>TextureAnimation</code> Tekstura animacji z
     *            AnimationManager.
     * @param animationLoops
     *            <code>long</code> Ilość odtwarzanych animacji. <br>
     *            Dla <code>animationLoops = -1</code> ilość odtwarzanych
     *            animacji wynosi nieskończoność.
     */
    public AnimatedSprite(String id, float x, float y, float width, float height, TextureAnimation animation, long animationLoops)
    {
	this.mActualFrame = 0;
	this.mActualLoop = 0;
	this.mMaxLoops = animationLoops;

	this.setAnimatedSpriteData(id, x, y, width, height, animation);
    }
    
    /**
     * Dodaje obiekt nasłuchujący animację.
     * @param listener <code>AnimationListener</code> obiekt listenera.
     */
    public void setAnimationListener(AnimationListener listener)
    {
	mAnimationListener = listener;
    }

    private void setAnimatedSpriteData(String id, float x, float y, float width, float height, TextureAnimation animation)
    {
	this.setName(id);
	this.mSize = new Size(width, height);
	this.mUpdateNeeded = true;
	this.mVertices = new float[] { x, y - height, // V1 - bottom left
		x, y, // V2 - top left
		x + width, y - height, // V3 - bottom right
		x + width, y, // V4 - top right
	};

	this.mVerticesCount = this.mVertices.length / 2;
	ByteBuffer byteBuffer = ByteBuffer.allocateDirect(this.mVertices.length * 4);
	byteBuffer.order(ByteOrder.nativeOrder());
	this.mVertexBuffer = byteBuffer.asFloatBuffer();
	this.mVertexBuffer.put(this.mVertices);
	this.mVertexBuffer.position(0);
	this.mTextureAnimation = animation;
	this.mTexture = animation.getTexture();
	this.mTextureRegion = animation.getRegion(mActualFrame);
    }

    @Override
    public void update(long elapsedTime)
    {
	if (!mVisible)
	{
	    return;
	}

	mActualLoop = (elapsedTime - mStartTime) / mTextureAnimation.getDuration();
	mActualFrame = ((elapsedTime - mStartTime) / mTextureAnimation.getFrameDuration()) % mTextureAnimation.getFrames();
	mTextureRegion = mTextureAnimation.getRegion(mActualFrame);
	mNotCreatedInScene = mActualFrame < 0 ? true : false;
	
	if(mNotCreatedInScene)
	{
	    return;
	}
	else if (mActualFrame == 0 && mActualLoop == 0 && mAnimationListener != null)
	{
	    mAnimationListener.onAnimationStarted(this);
	}
	else if (mActualFrame == 0 && mActualLoop != 0 && mAnimationListener != null)
	{
	    mAnimationListener.onNewAnimationCycle(this, mActualLoop);
	}
	
	boolean keepInScene = (mActualLoop < mMaxLoops || mMaxLoops == -1);
	
	if (mKeepInScene != keepInScene)
	{
	    mKeepInScene = keepInScene;

	    if (mAnimationListener != null && mKeepInScene == false)
	    {
		mAnimationListener.onAnimationEnded(this);
		return;
	    }
	}
    }
    
    @Override
    public void draw(GL10 gl)
    {
	if(mNotCreatedInScene)
	{
	    return;
	}
        
        super.draw(gl);
    }

    @Override
    public void dispose()
    {
	mTextureAnimation.dispose();

	mTextureAnimation = null;
	mAnimationListener = null;

	super.dispose();
    }
}
